#include <version.h>
#include <ukey/ukey.h>
#include <getopt.h>
#include <sys/resource.h>
#include <gwbase/base/logging.h>
#include <json/json.h>
#include <termios.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

#include "version.h"

const char* sopin = "DRA_123456";
const char* pin = "DRA_123456";

using namespace dicom;

static struct option long_options[] = {{"version", no_argument, 0, 'v'},
                                       {"help", no_argument, 0, 'h'},
                                       {"write", no_argument, 0, 'w'},
                                       {"format", no_argument, 0, 'f'},
                                       {"debug", no_argument, 0, 'd'},
                                       {0, 0, 0, 0}};

struct CmdArgs {
  bool show_help;
  bool show_version;
  bool write;
  bool format;
  bool list_keys;
  bool debug;

  CmdArgs()
      : show_help(false),
        show_version(false),
        write(false),
        format(false),
        list_keys(false),
        debug(false) {}

  bool TryParseOption(int argc, char** argv) {
    int c;
    while (true) {
      int option_index = 0;
      c = ::getopt_long(argc, argv, "vhdwfl", long_options, &option_index);
      if (c == -1) break;

      switch (c) {
        case 'v':
          this->show_version = true;
          break;
        case 'h':
          this->show_help = true;
          break;
        case 'w':
          this->write = true;
          break;
        case 'f':
          this->format = true;
          break;
        case 'l':
          this->list_keys = true;
          break;
        case 'd':
          this->debug = true;
          break;
        default:
          return false;
      }
    }
    return true;
  }
};

static void ShowVersion() { printf("DRA UKEY Tool Version: %s\n", VERSION); }

static void ShowHelp() {
  printf("DRA UKEY Tool Version: %s\n", VERSION);
  printf("Usage\n");
  printf("  dra_ukey [options]\n");
  printf("Options\n");
  printf("  -v, --version  show version\n");
  printf("  -l, --list     list all UKEYs\n");
  printf("  -h, --help     show this help\n");
  printf("  -w, --write    burn user account to UKEY\n");
  printf("  -f, --format   format UKEY\n");
  printf("  -d, --debug    enable debug log\n");
}

int DoFormat(ukey::TokenInfo& t) {
  Status s;
  ukey::UKeyHelper uh;
  s = uh.FormatToken(t.slot_id, "DRA_UKEY", sopin, pin);
  if (!s.ok()) {
    LOG_ERROR << "failed to format token: " << s;
    return -1;
  }

  return 0;
}

static int get_password(char* password, size_t outlen) {
  struct termios oldt, newt;

  /*saving the old settings of STDIN_FILENO and copy settings for resetting*/
  tcgetattr(STDIN_FILENO, &oldt);
  newt = oldt;

  /*setting the approriate bit in the termios struct*/
  newt.c_lflag &= ~(ECHO);

  /*setting the new bits*/
  tcsetattr(STDIN_FILENO, TCSANOW, &newt);

  /*reading the password from the console*/
  scanf("%s", password);

  /*resetting our old STDIN_FILENO*/
  tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
  return 0;
}

int DoWrite(ukey::TokenInfo& t) {
  Status s;
  ukey::UKeyHelper uh;

  char name[1024];
  char password[1024];

  printf("Account Email: ");
  scanf("%s", name);

  printf("Password: ");
  get_password(password, sizeof(password));
  printf("\n");

  s = uh.ClearSurDocCredential(t.slot_id, pin);
  if (!s.ok()) {
    LOG_ERROR << "failed to save clear old info from token: " << s;
    return -1;
  }

  s = uh.SaveSurDocCredential(t.slot_id, pin, name,
                              ByteArray::fromRawStr(password));
  if (!s.ok()) {
    LOG_ERROR << "failed to save user info to token: " << s;
    return -1;
  }

  printf("Save account info successfully!\n");
  return 0;
}

int DoReadData(ukey::TokenInfo& t) {
  Status s;
  ukey::UKeyHelper uh;
  std::string account;
  ByteArray data;
  s = uh.LoadSurDocCredential(t.slot_id, pin, &account, &data);
  if (!s.ok()) {
    LOG_ERROR << "can't get data from token: " << s;
    return -1;
  }

  /*
  Json::Value v;
  v["account"] = account;
  v["data"] = data.toHexStr();

  printf("%s\n", v.toStyledString().c_str());
  */
  printf("ukey_id=%s\n", t.serial_number.c_str());
  printf("user=%s\n", account.c_str());
  printf("password=%s\n", data.toRawStr().c_str());
  return 0;
}

int Main(void* arg) {
  Status s;
  CmdArgs* ra = static_cast<CmdArgs*>(arg);
  if (ra->debug) {
    dicom::SetLogLevel(dicom::LogLevel::kLogTrace);
  }

  ukey::UKeyHelper uh;
  std::vector<ukey::TokenInfo> tokens;
  s = uh.GetTokenList(&tokens);
  if (!s.ok()) {
    LOG_ERROR << "can't get token list: " << s;
    return -1;
  }

  if (tokens.size() == 0) {
    LOG_ERROR << "no token connected";
    return -1;
  }

  if (ra->list_keys) {
    for (size_t i = 0; i < tokens.size(); ++i) {
      ukey::TokenInfo& t = tokens[i];
      printf("slot=%d, serial_number=%s, label=%s\n", t.slot_id,
             t.serial_number.c_str(), t.label.c_str());
    }
    return 0;
  }

  if (ra->write) {
    return DoWrite(tokens[0]);
  }

  if (ra->format) {
    return DoFormat(tokens[0]);
  }

  return DoReadData(tokens[0]);
}

static void EnableCoreDump() {
  // core dumps may be disallowed by parent of this process; change that
  struct rlimit core_limits;
  core_limits.rlim_cur = core_limits.rlim_max = RLIM_INFINITY;
  setrlimit(RLIMIT_CORE, &core_limits);
}

int main(int argc, char** argv) {
  Status s;
  EnableCoreDump();

  CmdArgs ra;
  if (ra.TryParseOption(argc, argv) == false) {
    return -1;
  }

  if (ra.show_help) {
    ShowHelp();
    return 0;
  }

  if (ra.show_version) {
    ShowVersion();
    return 0;
  }

  s = ukey::UKeyHelper::GlobalInit();
  if (!s.ok()) {
    LOG_ERROR << "failed to init library: " << s;
    return -1;
  }
  int ret = Main(&ra);
  ukey::UKeyHelper::GlobalCleanup();
  return ret;
}
